package com.kele.modbus;


import com.kele.constants.ModbusFunction;
import com.kele.constants.ModbusProtocol;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

/**
 * modbus 帧解码器 <br/>
 * 功能码01,02,03,04是基于长度字段的帧解码器 <br/>
 * 功能码05,06,15,16是基于固定长度的帧解码器 <br/>
 */
public class ModbusFrameDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        if (in.readableBytes() < 2){
            return;
        }
        short firstByte = in.getUnsignedByte(in.readerIndex());
        short secondByte = in.getUnsignedByte(in.readerIndex() + 1);

        // 协议 Modbus-RTU,Modbus-TCP
        ModbusProtocol protocol;
        if (firstByte == 0 && secondByte == 0){
            protocol = ModbusProtocol.MODBUS_TCP;
        } else {
            protocol = ModbusProtocol.MODBUS_RTU;
        }

        if (ModbusProtocol.MODBUS_RTU.equals(protocol)){ // RTU

            int address = firstByte;
            int function = secondByte;

            // 异常
            if (isExceptionFunction(function)){
                decodeByFixedLength(in, out, 5);
                return;
            }

            // 正常
            // 根据功能码区分
            if (isReadingFunction(function)) { // 读功能
                decodeByLengthField(in, out, 2, 2);

            } else if (isWritingFunction(function)) { // 写功能
                decodeByFixedLength(in, out, 8);

            } else { // 其他
                out.add(in.readRetainedSlice(in.readableBytes()));
            }

        } else { // TCP
            if (in.readableBytes() < 6){
                return;
            }
            decodeByLengthField(in, out, 5, 0);
        }
    }

    /**
     * 是否是异常功能码
     * @param function 功能码
     * @return
     */
    private boolean isExceptionFunction(int function) {
        return (function & 0x80) != 0;
    }

    //

    /**
     * 判断是否为读功能
     * @param function 功能码
     * @return
     */
    private boolean isReadingFunction(int function) {
        return function == ModbusFunction.READ_COILS
                || function == ModbusFunction.READ_DISCRETE_INPUTS
                || function == ModbusFunction.READ_HOLDING_REGISTERS
                || function == ModbusFunction.READ_INPUT_REGISTERS;
    }

    /**
     * 判断是否为写功能
     * @param function 功能码
     * @return
     */
    private boolean isWritingFunction(int function) {
        return function == ModbusFunction.WRITE_SINGLE_COIL
                || function == ModbusFunction.WRITE_SINGLE_REGISTER
                || function == ModbusFunction.WRITE_MULTIPLE_COILS
                || function == ModbusFunction.WRITE_MULTIPLE_REGISTERS;
    }

    /**
     * 根据固定长度解析
     * @param in
     * @param out
     * @param fixedLength 长度
     */
    private void decodeByFixedLength(ByteBuf in, List<Object> out, int fixedLength){
        Object object = decodeByFixedLength(in, fixedLength);
        if (object != null){
            out.add(object);
        }
    }

    /**
     * 解码
     * @param in
     * @param fixedLength
     * @return
     */
    private Object decodeByFixedLength(ByteBuf in, int fixedLength){
        if (in.readableBytes() < fixedLength) {
            return null;
        } else {
            return in.readRetainedSlice(fixedLength);
        }
    }

    /**
     * 根据长度字段解析
     * @param in
     * @param out
     * @param lengthFieldIndex
     * @param additional
     */
    private void decodeByLengthField(ByteBuf in, List<Object> out, int lengthFieldIndex, int additional){
        if (in.readableBytes() < lengthFieldIndex){
            return;
        }
        short lengthField = in.getUnsignedByte(in.readerIndex() + lengthFieldIndex);
        if (in.readableBytes() < lengthFieldIndex + lengthField + additional){
            return;
        }
        int length = lengthFieldIndex + 1 + lengthField + additional;
        if (in.readableBytes() >= length){
            out.add(in.readRetainedSlice(length));
        }
    }
}
